'''
Created on 2011-8-30

@author: Administrator
'''
import logging
import protocol
import db
import copy


class Client():
    def __init__(self):
        self.special  = False
        self.enable  = False
        self.system = None
        self.os = None
        self.bits = None
        self.mac_addr = None
        self.ip_addr = None
        self.name = None
        self.groupid = None
        self.dbid  = None
        self.connectid  = None
        self.cpu_percent = 0.0
        self.mem_percent = 0.0
        self.free_disk = 0
        self.tasklist = {}
        self.templatelist = {}
        self.special = False
        
    def AddTask(self, templateid, taskid, subid):
        if taskid not in self.tasklist:
            self.tasklist[taskid] = set()
        
        if subid != None:
            self.tasklist[taskid].add(subid)
        
        if templateid not in self.templatelist:
            self.templatelist[templateid] = set()
        self.templatelist[templateid].add(taskid)
            
        
    def RemoveTask(self, templateid, taskid, subid):
        try:
            l = self.tasklist[taskid]
            if subid != None:
                l.remove(subid)
            if len(l) == 0:
                self.tasklist.pop(taskid)
                self.templatelist[templateid].remove(taskid)
                if len(self.templatelist[templateid]) == 0:
                    self.templatelist.pop(templateid) 
        except Exception as e:
            logging.exception(e)     
                
    

class ClientManager():
    def __init__(self, config, connnectmanager, dbmanager):
        self.clients = set()
        self.pending_client = {}
        self.types = {}
        self.allclients = {}
        self.group_list = {}
        self.connectid2dbid = {}
        self.oslist = {}
        self.connect_manager = connnectmanager
        self.connect_manager.AddConnectHandler(self.OnClientConnect)
        self.connect_manager.AddDisconnectHandler(self.OnClientDisConnect)
        self.connect_manager.RegisterProtocol(protocol.c2s_client_info, self.OnClientInfo)
        self.connect_manager.RegisterProtocol(protocol.c2s_preform_info_request, self.OnPerformanInfoRequest)
        
        self.db_manager = dbmanager
        self.db_manager.RegisterProtocol(db.dbprotocol.machine_dbid_respond, self.MachineDbIDRespond)
        self.db_manager.RegisterProtocol(db.dbprotocol.insert_os_respond, self.OnInsertNewOSRespond)
        
        self.config = config
        
        self.disconnect_handler = []
        
    def Init(self):
        for v in self.db_manager.LoadOSList():
            self.oslist[v.Name] = v.Id
            self.oslist[v.Id] = v.Name
    
    def OnClientConnect(self, connectid):
        c = Client()
        c.ip_addr = self.connect_manager.GetIP(connectid)
        c.connectid = connectid
        self.pending_client[connectid] = c
        
    def OnClientDisConnect(self, connectid):
        c = None
        
        if connectid in self.connectid2dbid:
            id = self.connectid2dbid.pop(connectid)
            c  = self.allclients.pop(id)
            if c.enable == True:
                self.clients.remove(c)
                self.RemoveInClassify(c)
        
        if connectid in self.pending_client:
            c = self.pending_client.pop(connectid)
            
        if c:
            if c.groupid:
                self.group_list[c.groupid].remove(c)
            
            if c.dbid:
                self.UpdateMachineInfoRequest(c, 3, False)
                for v in self.disconnect_handler:
                    v(c.dbid, c.tasklist)
        
        
    def GetClient(self, id):
        if id in self.allclients:
            return self.allclients[id]
        
        if id in self.connectid2dbid:
            return self.allclients[self.connectid2dbid[id]]
        
        raise ValueError('id not in clients')
    
    
    def IsLogin(self, connectid):
        try:
            self.GetClient(connectid)
            return True
        except:
            return False
        
        
    def GetClientByCondition(self, machineinfo):
        machines = []
        if len(machineinfo.machinelist) != 0: 
            for v in machineinfo.machinelist:
                if v in self.allclients:
                    machine = self.allclients[v]
                    if machine in self.clients:
                        machines.append(machine)
            return self.SortBestMachine(machines)
        
        
        if machineinfo.groupid:
            if machineinfo.groupid  in self.group_list:
                machines.extend(self.group_list[machineinfo.groupid])
                return self.SortBestMachine(machines)
            else:
                return []
        
        labels = []
        if machineinfo.os:
            labels.append(self.oslist[machineinfo.os])
        
        templist = set()
        if labels:
            for v in labels:
                if v in self.types:
                    if len(machines) == 0:
                        templist  = copy.copy(self.types[v])
                    else:
                        templist  = machines & self.types[v]
            return self.SortBestMachine([].extend(templist))
        
        machines = [ v for v in self.clients if v.special == False]             
        return self.SortBestMachine(machines)
    
    def SortBestMachine(self, machinelist):
        machinelist = [v for v in machinelist if v.cpu_percent < 90 and v.free_disk > 300]
        machinelist.sort(key= lambda c : (c.cpu_percent * 0.8 + c.mem_percent *0.2))
        return machinelist
    
    def AddInClassify(self, client):
        if client.system not in self.types:
            self.types[client.system] = set()
        self.types[client.system].add(client)
        
        if client.os not in self.types:
            self.types[client.os] = set()
        self.types[client.os].add(client)
        
        if client.bits not in self.types:
            self.types[client.bits] = set()
        self.types[client.bits].add(client)
    
    def RemoveInClassify(self, client):
        for v in self.types.values():
            if client in v:
                v.remove(client)
         
    def OnClientInfo(self, clientid, info):
        if clientid not in self.pending_client:
            logging.warn('%s not in manager', clientid)
            return
        
        c  = self.pending_client[clientid]
        c.system = info.system
        c.bits = info.bits
        c.mac_addr = info.mac_addr
        c.os = info.os
        c.name = info.name
        
        self.UpdateMachineInfoRequest(c, 1)
        #self.DoUploadFileConifg(clientid, self.config)
    
    def OnPerformanInfoRequest(self, clientid, request):
        if clientid not in self.connectid2dbid:
            logging.warn('%s not in manager', clientid)
            return
        
        c = self.allclients[self.connectid2dbid[clientid]]
        c.cpu_percent = request.cpu_percent
        c.mem_percent = request.mem_percent
        c.free_disk = request.free_disk
    
    def DoClientExit(self, connectid, message):
        request = protocol.S2CClientExits()
        request.message = message
        self.connect_manager.DoSend(connectid, request)
        
        
        
    def UpdateMachineInfoRequest(self, c, status, isrespond = True):
        request = db.dbprotocol.UpdateMachineInfoRequest()
        request.isrespond = isrespond
        request.connectid = c.connectid
        request.data = {'MacAddress':c.mac_addr,
                        'Name':c.name,
                        'Ip':c.ip_addr,
                        'MachineStatusId':status,
                        'IsX64':(c.bits != '32bit')}
        if c.os in self.oslist:
            request.data['OSId'] = self.oslist[c.os]
        else:
            self.InsertNewOS(c.os)
            
        self.db_manager.PutRequest(request)
        
        
    def MachineDbIDRespond(self, respond):
        if respond.connectid not in self.pending_client:
            logging.warn(' db respond macaddress not exits')
            return
        
        c = self.pending_client.pop(respond.connectid)
        
        if respond.dbid in self.allclients:    
            self.DoClientExit(respond.connectid, 'Start multiple client !!')
            self.connect_manager.CloseConnecter(respond.connectid)
            return 
        
        c.dbid = respond.dbid
        c.groupid = respond.groupid
        c.enable = respond.enable
        c.special = respond.special
        
        self.connectid2dbid[c.connectid] = c.dbid
        self.allclients[c.dbid] = c
        
        if  c.enable == True:
            self.clients.add(c)
            if respond.groupid not in self.group_list:
                self.group_list[respond.groupid] = set()
            self.group_list[respond.groupid].add(c)
            if respond.special == False:
                self.AddInClassify(c)
        
        self.DoUploadFileConifg(respond.connectid, self.config)

    def SetEnableMachine(self, id, isenable):
        if id not in self.allclients:
            raise ValueError('no exits or connect machine')
        
        machine = self.allclients[id]
        if machine.enable == isenable:
            return
        
        if isenable == False:
            if len(machine.tasklist) != 0:
                raise ValueError('task running on the machine')
            machine.enable = False
            self.clients.pop(machine)
            self.pending_client[machine.con] = machine
        elif isenable == True:
            machine.enable = True
            self.pending_client.pop(machine.connectid)
            self.clients.add(machine)
            
    def SetMachineGroup(self, id, groupid):
        if id not in self.allclients:
            raise ValueError('no exits or connect machine')
        
        machine = self.allclients[id]
        
        if machine.groupid == groupid:
            return 
        
        if machine.groupid:
            self.group_list[machine.groupid].remove(machine)
        
        if groupid not in self.group_list:
            self.group_list[groupid] = set()
        
        self.group_list[groupid].add(machine)
         
        
    def DoUploadFileConifg(self, connectid, config):
        request = protocol.S2CConfigRequest()
        request.ftpip = config.uploadcfg.ip
        request.ftpport = config.uploadcfg.port
        request.ftpuser = config.uploadcfg.username
        request.ftppassword = config.uploadcfg.password
        request.svnpath = config.svncfg.path
        request.svnuser = config.svncfg.user
        request.svnpassword = config.svncfg.password
        self.connect_manager.DoSend(connectid, request)
        
    def InsertNewOS(self, name):
        request = db.dbprotocol.InsertNewOSRequest(name)
        self.db_manager.PutRequest(request)
        
    def OnInsertNewOSRespond(self, respond):
        if respond.result == True:
            if respond.name in self.oslist:
                logging.warn('name aleady in oslist')
                return
            self.oslist[respond.name] = respond.dbid
            
    def AddTaskToMachine(self, clientid, templateid, taskid, subid = None):
        client  = self.allclients[clientid]
        client.AddTask(templateid, taskid, subid)
        
    def RemoveTaskOnMachine(self, clientid, templateid, taskid, subid = None):
        client  = self.allclients[clientid]
        client.RemoveTask(templateid, taskid, subid)
        
        
